﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Criterion;
using NHibernate.Tool.hbm2ddl;
using ProjectBaseData.NHibernateSessionManagement;

namespace ProjectBaseData
{
    public class AbstractNHibernateDAO<T, IdT> : IDAO<T, IdT>
    {
        protected AbstractNHibernateDAO()
        {
        }

        /// <summary>
        /// Обновляет схему.
        /// </summary>
        public void UpdateSchema()
        {
            var cfg = new Configuration();
            cfg.Configure(SessionFactoryConfigPath);

            var created = true;
            try
            {
                CreateSchema(cfg);
            }
            catch
            {
                created = false;
            }
            if (!created)
            {
                DropSchema(cfg);
                CreateSchema(cfg);
            }
        }

        private void CreateSchema(Configuration cfg)
        {
            new SchemaExport(cfg).Create(true, true);
        }

        private void DropSchema(Configuration cfg)
        {
            new SchemaExport(cfg).Drop(true, true);
        }

        /// <param name="sessionFactoryConfigPath">Fully qualified path of the session factory's config file</param>
        public AbstractNHibernateDAO(string sessionFactoryConfigPath)
        {
           // Check.Require(!string.IsNullOrEmpty(sessionFactoryConfigPath),
            //    "sessionFactoryConfigPath may not be null nor empty");

            SessionFactoryConfigPath = sessionFactoryConfigPath;
        }

        /// <summary>
        /// Loads an instance of type T from the DB based on its ID.
        /// </summary>
        public T GetByID(IdT id, bool shouldLock)
        {
            T entity;
            using (var session = NHibernateSession)
            {
                using (var t = session.BeginTransaction())
                {
                    if (shouldLock)
                    {
                        entity = (T)session.Load(persitentType, id, LockMode.Upgrade);
                    }
                    else
                    {
                        entity = (T)session.Load(persitentType, id);
                    }
                    t.Commit();
                }
            }

            return entity;
        }

        /// <summary>
        /// Loads every instance of the requested type with no filtering.
        /// </summary>
        public List<T> GetAll()
        {
            return GetByCriteria();
        }

        /// <summary>
        /// Loads every instance of the requested type using the supplied <see cref="ICriterion" />.
        /// If no <see cref="ICriterion" /> is supplied, this behaves like <see cref="GetAll" />.
        /// </summary>
        public List<T> GetByCriteria(params ICriterion[] criterion)
        {
            List<T> list = new List<T>();
            using (var session = NHibernateSession)
            {
                using (var t = session.BeginTransaction())
                {
                    ICriteria criteria = session.CreateCriteria(persitentType);

                    foreach (ICriterion criterium in criterion)
                    {
                        criteria.Add(criterium);
                    }

                    list = criteria.List<T>() as List<T>;
                    t.Commit();
                }
            }
            return list;
        }

        public List<T> GetByExample(T exampleInstance, params string[] propertiesToExclude)
        {
            List<T> list = null;
            using (var session = NHibernateSession)
            {
                using (var t = session.BeginTransaction())
                {
                    ICriteria criteria = session.CreateCriteria(persitentType);
                    Example example = Example.Create(exampleInstance);

                    foreach (string propertyToExclude in propertiesToExclude)
                    {
                        example.ExcludeProperty(propertyToExclude);
                    }

                    criteria.Add(example);
                    list = criteria.List<T>() as List<T>;
                    t.Commit();

                }
            }
            return list;
        }

        /// <summary>
        /// Looks for a single instance using the example provided.
        /// </summary>
        /// <exception cref="NonUniqueResultException" />
        public T GetUniqueByExample(T exampleInstance, params string[] propertiesToExclude)
        {
            List<T> foundList = GetByExample(exampleInstance, propertiesToExclude);

            if (foundList.Count > 1)
            {
                throw new NonUniqueResultException(foundList.Count);
            }

            if (foundList.Count > 0)
            {
                return foundList[0];
            }
            else
            {
                return default(T);
            }
        }

        /// <summary>
        /// For entities that have assigned ID's, you must explicitly call Save to add a new one.
        /// See http://www.hibernate.org/hib_docs/reference/en/html/mapping.html#mapping-declaration-id-assigned.
        /// </summary>
        public virtual T Save(T entity)
        {
            //NHibernateSession.Evict(entity);
            using (var session = NHibernateSession)
            {
                using (var t = session.BeginTransaction())
                {
                    session.SaveOrUpdate(entity);
                    t.Commit();
                }
            }
            //NHibernateSession.Persist(entity);
            //NHibernateSession.Flush();
            return entity;
        }

        /// <summary>
        /// For entities with automatatically generated IDs, such as identity, SaveOrUpdate may 
        /// be called when saving a new entity.  SaveOrUpdate can also be called to _update_ any 
        /// entity, even if its ID is assigned.
        /// </summary>
        public T SaveOrUpdate(T entity)
        {
            //entity = (T)NHibernateSession.Merge(entity);
            return Save(entity);
        }

        /// <summary>
        /// Удаление записи
        /// </summary>
        /// <param name="entity"></param>
        public void Delete(T entity)
        {
            //Post p;
            //NHibernateSession.CreateQuery("select f from Feature f where f.post="+p).List()
            using (var session = NHibernateSession)
            {
                using (var t = session.BeginTransaction())
                {
                    session.Delete(entity);
                    t.Commit();
                }
            }
        }

        /// <summary>
        /// Commits changes regardless of whether there's an open transaction or not
        /// </summary>
        public void CommitChanges()
        {
            if (NHibernateSessionManager.Instance.HasOpenTransactionOn(SessionFactoryConfigPath))
            {
                NHibernateSessionManager.Instance.CommitTransactionOn(SessionFactoryConfigPath);
            }
            else
            {
                // If there's no transaction, just flush the changes
                NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath).Flush();
            }
        }

        public void SaveValue(string tableName, string value)
        {
            using (var session = NHibernateSession)
            {
                using (var t = NHibernateSession.BeginTransaction())
                {
                    IQuery query = NHibernateSession.CreateSQLQuery("insert into " + tableName + " values ('" + value + "', '', 0)");
                    query.ExecuteUpdate();
                    t.Commit();
                }
            }
        }

        /// <summary>
        /// Exposes the ISession used within the DAO.
        /// </summary>
        protected ISession NHibernateSession
        {
            get
            {
                return NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath);
            }
        }

        public void SaveList(IList<T> domains, int maxCount = 100)
        {
            using (var session = NHibernateSession)
            {
                //Цикл по всем объектам в списке
                int i = 0;
                while (i < domains.Count)
                {
                    //Создание транзакции
                    ITransaction transaction = session.BeginTransaction();
                    for (int j = 0; j < maxCount; j++)
                    {
                        //Добавление объекта в сессию
                        if (i + j < domains.Count)
                        {
                            session.Save(domains[i + j]);
                        }
                    }
                    //Транзакция в базу
                    transaction.Commit();
                    for (int j = 0; j < maxCount; j++)
                    {
                        //Удаление объекта из сессии
                        if (i + j < domains.Count)
                        {
                            session.Evict(domains[i + j]);
                        }
                    }
                    i = i + maxCount;
                }
            }
        }

        private readonly Type persitentType = typeof(T);
        protected readonly string SessionFactoryConfigPath;
        public string SessionConfigPath { get { return SessionFactoryConfigPath; } }
    }
}
